/****************************************************************************
 **
 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 ** All rights reserved.
 ** Contact: Nokia Corporation (qt-info@nokia.com)
 **
 ** This file is part of the examples of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:BSD$
 ** You may use this file under the terms of the BSD license as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
 **     the names of its contributors may be used to endorse or promote
 **     products derived from this software without specific prior written
 **     permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/

#include <assert.h>
#include <QtGui>
#include <queue>
#include <vector>
#include <math.h>

#include "match.h"
#include "scribblearea.h"
#include "Core.h"
#include "ShadowDB.h"

vector<Record> images;
bool xd = false;
	ScribbleArea::ScribbleArea(QWidget *parent)
: QWidget(parent)
{
	setAttribute(Qt::WA_StaticContents);
	modified = false;
	scribbling = false;
	setFixedSize(300, 300);
	resizeImage(&image, QSize(300, 300));
	myPenWidth = 1;
	myPenColor = Qt::blue;
	shadowVisibility = 0.3;
	mode = 0;
}

bool ScribbleArea::openImage(const QString &fileName)
{
	QImage loadedImage;
	if (!loadedImage.load(fileName))
		return false;

	QSize newSize = loadedImage.size().expandedTo(size());
	resizeImage(&loadedImage, newSize);
	image = loadedImage;
	modified = false;
	update();
	return true;
}

bool ScribbleArea::saveImage(const QString &fileName, const char *fileFormat)
{
	QImage visibleImage = image;
	resizeImage(&visibleImage, size());

	if (visibleImage.save(fileName, fileFormat)) {
		modified = false;
		return true;
	} else {
		return false;
	}
}

void ScribbleArea::setPenColor(const QColor &newColor)
{
	myPenColor = newColor;
}

void ScribbleArea::setPenWidth(int newWidth)
{
	myPenWidth = newWidth;
}

void ScribbleArea::setVisibility(double newVisibility)
{
	shadowVisibility = newVisibility;
	update();
}

void ScribbleArea::setEraserMode(int newMode)
{
	if(newMode==0){
		mode = 1;
		myPenWidth = 3;
		myPenColor = Qt::white;
	}
	else{
		mode = 0;
		myPenWidth = 1;
		myPenColor = Qt::blue;
	}
}

void ScribbleArea::clearImage()
{
	image.fill(qRgb(255, 255, 255));
	modified = true;
	shadow.fill(qRgb(0, 0, 0));
	mode = 0;
	update();
}

void ScribbleArea::mousePressEvent(QMouseEvent *event)
{
	if (event->button() == Qt::LeftButton) {
		lastPoint = event->pos();
		scribbling = true;
	}
}

void ScribbleArea::mouseMoveEvent(QMouseEvent *event)
{
	if ((event->buttons() & Qt::LeftButton) && scribbling)
		drawLineTo(event->pos());
}

void ScribbleArea::mouseReleaseEvent(QMouseEvent *event)
{
	if (event->button() == Qt::LeftButton && scribbling) {
		drawLineTo(event->pos());
		scribbling = false;
		lastCursor = event->pos();
		if(mode==0)
			updateShadow();
	}
}

void ScribbleArea::updateShadow(){
	fprintf(stderr, "computing...\n");
	//TODO: compute candidate
	ShadowDB db;
	db.load("genDB/small_set.db");
	unsigned char data[300*300];
	for(int i=0;i<image.height();i++)
		for(int j=0;j<image.width();j++)
			data[i*image.width()+j] = qGray(image.pixel(j,i))==255 ? 0 : 255;
	const vector<Record> images = db.query(image.height(),image.width(),data);
	
	fprintf(stderr, "query done\n");
	//TODO: change this back
	/*
	if(!xd){
		 images = db.query(image.height(),image.width(),data);
		 xd = true;
	}
	*/
	

	int n = images.size();
	if(n>30) n=30;
	Match match[100];
	for(int i=0;i<n;++i){
		char fname[100];
		sprintf(fname,"./genDB/small_set/%05d.jpg", images[i].fileID);
		//fprintf(stderr, "%s %d %d\n", fname, images[i].position.x, images[i].position.y);
		assert(match[i].image.load(fname));
		match[i].dx = images[i].position.x;
		match[i].dy = images[i].position.y;
	}

	//image alignment
	for(int k=0;k<n;++k){
		double maximum=0;
		int dx = match[k].dx, dy = match[k].dy;
		for(int x=-7;x<7;x+=2){
			double tmp=0;
			for(int i=0;i<image.width();++i)
				for(int j=0;j<image.height();++j)
					if(qGray(image.pixel(i,j))!=255 &&
							match[k].image.valid(i+dx+x, j+dy) && 
							qGray(match[k].image.pixel(i+dx+x, j+dy))==255){
						//tmp += ((j+eps) / sqrt((i+eps)*(i+eps) + (j+eps)*(j+eps))) *
								//((j+dy+eps) / sqrt((i+dx+x+eps)*(i+dx+x+eps) + (j+dy+eps)*(j+dy+eps)));
						tmp++;
					}
			if(tmp>maximum){
				match[k].dx = dx + x;
				maximum = tmp;
			}
		}

		maximum = 0;
		for(int y=-7;y<7;y+=2){
			double tmp=0;
			for(int i=0;i<image.width();++i)
				for(int j=0;j<image.height();++j)
					if(qGray(image.pixel(i,j))!=255 &&
							match[k].image.valid(i+dx, j+dy+y) && 
							qGray(match[k].image.pixel(i+dx, j+dy+y))==255)
						//tmp += ((i+eps) / sqrt((i+eps)*(i+eps) + (j+eps)*(j+eps))) *
								//((i+dx+eps) / sqrt((i+dx+eps)*(i+dx+eps) + (j+dy+y+eps)*(j+dy+y+eps)));
						tmp++;
			if(tmp>maximum){
				match[k].dy = dy + y;
				maximum = tmp;
			}
		}
	}
	fprintf(stderr, "image alignment done\n");

	//image weighting
	int ex[] = { 1, 1, 0, -1}, ey[]={0, 1, 1, 1};
	int o[4][300][300];
	for(int l=0;l<4;++l){
		for(int i=0;i<image.width();++i)
			for(int j=0;j<image.height();++j){
				if(qGray(image.pixel(i,j))!=255 && 
						image.valid(i+ex[l], j+ey[l]) &&
						qGray(image.pixel(i+ex[l],j+ey[l]))!=255)
					o[l][i][j] = 1;
				else
					o[l][i][j] = 0;
			}
	}
	
	std::priority_queue<double, std::vector<double>, std::greater<double> > pq;
	for(int k=0;k<n;++k){
		int op[4][300][300];
		match[k].V = 0;
		match[k].h = 0;
		for(int l=0,m=2;l<4;++l,m=(m+1)%4){
			for(int i=0;i<match[k].image.width();++i)
				for(int j=0;j<match[k].image.height();++j){
					if(qGray(match[k].image.pixel(i,j))==255 &&
							match[k].image.valid(i+ex[l], j+ey[l]) &&
							qGray(match[k].image.pixel(i+ex[l],j+ey[l]))==255)
						op[l][i][j] = 1;
					else
						op[l][i][j] = 0;
					match[k].V += o[l][i][j]*op[l][i][j];
					match[k].h +=	o[l][i][j]*op[l][i][j] - o[m][i][j]*op[l][i][j];
				}	
		}
		pq.push(match[k].h);
		if(pq.size()>5)
			pq.pop();
	}
	double hs=0;
	while(!pq.empty()){
		hs += pq.top();
		pq.pop();
	}
	hs /= 5;
	for(int k=0;k<n;++k){
		match[k].v = (match[k].h - 0.5*hs)/(0.5*hs);
		match[k].v = match[k].v * match[k].v;
	}
		
	double normalize=0;
	for(int k=0;k<n;++k){
		normalize += match[k].v * match[k].V;
		//normalize +=  match[k].V;
		//fprintf(stderr, "%lf\n", match[k].V);
	}

	QImage output(image);
	output.fill(qRgb(0, 0, 0));
	for(int i=0;i<output.width();++i)
		for(int j=0;j<output.height();++j){
			double r=0, g=0, b=0;
			for(int k=0;k<n;++k)
				if(match[k].image.valid(i+match[k].dx, j+match[k].dy)){
					double coeff = match[k].v*match[k].V/normalize;
					//double coeff = match[k].V/normalize;
					r += coeff*qRed(match[k].image.pixel(i+match[k].dx, j+match[k].dy));
					g += coeff*qGreen(match[k].image.pixel(i+match[k].dx, j+match[k].dy));
					b += coeff*qBlue(match[k].image.pixel(i+match[k].dx, j+match[k].dy));
				}
			output.setPixel(i, j, qRgb((int)r, (int)g, (int)b));
		}
	shadow = output;

	fprintf(stderr, "done!\n");
}

void ScribbleArea::paintEvent(QPaintEvent *event)
{
	QPainter painter(this);
	QRect dirtyRect = event->rect();
	QImage output(image);
	for(int i=0;i<shadow.width();++i)
		for(int j=0;j<shadow.height();++j){
			double dists = (i-lastCursor.x())*(i-lastCursor.x())+(j-lastCursor.y())*(j-lastCursor.y());
			double coeff = shadowVisibility*exp(-dists/2/120/120);
			//double coeff = shadowVisibility;
			int r = qRed(image.pixel(i,j))   - qRed(shadow.pixel(i,j))*coeff;
			int g = qGreen(image.pixel(i,j)) - qGreen(shadow.pixel(i,j))*coeff;
			int b = qBlue(image.pixel(i,j))  - qBlue(shadow.pixel(i,j))*coeff;
			if(r<0) r = 0;
			if(g<0) g = 0;
			if(b<0) b = 0;
			output.setPixel(i, j, qRgb(r,g,b));
		}
	//painter.drawImage(dirtyRect, output, dirtyRect);
	painter.drawImage(0, 0, output);
}

void ScribbleArea::resizeEvent(QResizeEvent *event)
{
	if (width() > image.width() || height() > image.height()) {
		int newWidth = qMax(width() + 128, image.width());
		int newHeight = qMax(height() + 128, image.height());
		resizeImage(&image, QSize(newWidth, newHeight));
		update();
	}
	QWidget::resizeEvent(event);
}

void ScribbleArea::drawLineTo(const QPoint &endPoint)
{
	QPainter painter(&image);
	painter.setPen(QPen(myPenColor, myPenWidth, Qt::SolidLine, Qt::RoundCap,
				Qt::RoundJoin));
	painter.drawLine(lastPoint, endPoint);
	modified = true;

	//int rad = (myPenWidth / 2) + 2;
	update();
	//	update(QRect(lastPoint, endPoint).normalized()
	//			.adjusted(-rad, -rad, +rad, +rad));
	lastPoint = endPoint;
}

void ScribbleArea::resizeImage(QImage *image, const QSize &newSize)
{
	if (image->size() == newSize)
		return;

	QImage newImage(newSize, QImage::Format_RGB32);
	newImage.fill(qRgb(255, 255, 255));
	QPainter painter(&newImage);
	painter.drawImage(QPoint(0, 0), *image);
	*image = newImage;
}

void ScribbleArea::print()
{
#ifndef QT_NO_PRINTER
	QPrinter printer(QPrinter::HighResolution);

	QPrintDialog *printDialog = new QPrintDialog(&printer, this);
	if (printDialog->exec() == QDialog::Accepted) {
		QPainter painter(&printer);
		QRect rect = painter.viewport();
		QSize size = image.size();
		size.scale(rect.size(), Qt::KeepAspectRatio);
		painter.setViewport(rect.x(), rect.y(), size.width(), size.height());
		painter.setWindow(image.rect());
		painter.drawImage(0, 0, image);
	}
#endif // QT_NO_PRINTER
}
